package com.biz.primus.ms.depot.service;

import com.alibaba.fastjson.JSONObject;
import com.biz.primus.base.constant.EmpStreamConstant;
import com.biz.primus.base.exception.BizBusinessException;
import com.biz.primus.base.exception.utils.AssertUtils;
import com.biz.primus.common.utils.CopyUtils;
import com.biz.primus.common.utils.DateUtil;
import com.biz.primus.common.vo.SpringPageVO;
import com.biz.primus.model.depot.enums.EmployeeRankEnum;
import com.biz.primus.model.depot.exception.DepotException;
import com.biz.primus.model.depot.vo.*;
import com.biz.primus.ms.base.service.AbstractBaseService;
import com.biz.primus.ms.base.vo.SpringPageToSpringPageVO;
import com.biz.primus.ms.depot.component.annotation.EmployeeChangeMark;
import com.biz.primus.ms.depot.dao.po.Depot;
import com.biz.primus.ms.depot.dao.po.EmpSpread;
import com.biz.primus.ms.depot.dao.po.Employee;
import com.biz.primus.ms.depot.dao.po.UserEmpInfo;
import com.biz.primus.ms.depot.dao.redis.UserEmpInfoRedisDao;
import com.biz.primus.ms.depot.dao.redis.WeChatAccessRedisDao;
import com.biz.primus.ms.depot.dao.repository.DepotRepository;
import com.biz.primus.ms.depot.dao.repository.EmpSpreadRepository;
import com.biz.primus.ms.depot.dao.repository.EmployeeRepository;
import com.biz.primus.ms.depot.dao.repository.UserEmpInfoRepository;
import com.biz.primus.ms.depot.dao.ro.UserEmpInfoRo;
import com.biz.primus.ms.depot.dao.specfication.EmpSpreadSpeccification;
import com.biz.primus.ms.depot.dao.specfication.EmployeeSpecification;
import com.biz.primus.ms.depot.dao.specfication.EmployeeTmsSpecification;
import com.biz.primus.ms.depot.trans.UserEmpInfo2Ro;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * EmployeeService
 *
 * @author jia JKLiues
 * @date 2017年10月12日
 */
@Service
@Slf4j
public class EmployeeService extends AbstractBaseService {

    @Autowired
    private WeChatAccessRedisDao weChatAccessRedisDao;
    @Autowired
    private EmployeeRepository employeeRepository;
    @Autowired
    private UserEmpInfoRepository empInfoRepository;
    @Autowired
    private UserEmpInfoRedisDao userEmpInfoRedisDao;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private EmpSpreadRepository empSpreadRepository;
    @Autowired
    private DepotRepository depotRepository;

    @Value("${primus.we-chat.token-url}")
    private String tokenUrl;
    @Value("${primus.we-chat.app-id}")
    private String appId;
    @Value("${primus.we-chat.secret}")
    private String secret;

    /**
     * 获取二维码ticket的URL
     */
    @Value("${primus.we-chat.qr-code-url}")
    private String qrCodeTicketUrl;

    /**
     * 根据Id查员工信息
     *
     * @param employeeId 员工ID
     * @return 员工信息详情
     */
    public EmployeeVo findByEmployeeId(Long employeeId) {
        AssertUtils.notNull(employeeId, DepotException.EMP_ID_IS_NULL);
        try {
            String empCode = empIdCodeCache.get(employeeId);
            return this.findByEmployeeCode(empCode);
        } catch (ExecutionException e) {
            e.printStackTrace();
            throw new BizBusinessException(DepotException.EMP_NOT_EXIST);
        }
    }

    /**
     * 根据编号查员工信息
     *
     * @param employeeCode 员工编码
     * @return 员工信息详情
     */
    @SuppressWarnings("unchecked")
    public EmployeeVo findByEmployeeCode(String employeeCode) {
        AssertUtils.hasLength(employeeCode, DepotException.EMP_CODE_IS_NULL);
        try {
            Employee employee = employeeCacheLoader.get(employeeCode);
            AssertUtils.notNull(employee, DepotException.EMP_NOT_EXIST);
            EmployeeVo vo = new EmployeeVo();
            CopyUtils.copyProperties(employee, vo);
            vo.setEmployeeId(String.valueOf(employee.getId()));
            UserEmpInfoRo one = userEmpInfoRedisDao.findOne(employeeCode);
            vo.setQrCode(one == null ? null : one.getTicket());
            return vo;
        } catch (Exception e) {
            log.error("根据编号{}未查询到员工信息,{}", employeeCode, e.getMessage());
            e.printStackTrace();
            throw new BizBusinessException(DepotException.EMP_NOT_EXIST);
        }
    }

    /**
     * 根据门店编码查员工
     *
     * @param depotCode 门店编码
     */
    @SuppressWarnings("unchecked")
    public List<EmployeeVo> findEmployeesByDepotCode(String depotCode) {
        AssertUtils.hasLength(depotCode, DepotException.DEPOT_CODE_IS_NULL);
        List<Employee> pos = new ArrayList<>();
        try {
            pos = empDepotListCache.get(depotCode);
        } catch (Exception e) {
            log.error("根据门店编码{}查员工,{}", depotCode, e.getMessage());
            e.printStackTrace();
        }
        return Optional.of(pos).orElse(new ArrayList<>()).stream().map(po -> {
            EmployeeVo vo = new EmployeeVo();
            CopyUtils.copyProperties(po, vo);
            vo.setEmployeeId(String.valueOf(po.getId()));
            return vo;
        }).collect(Collectors.toList());
    }

    /**
     * 分页查询员工信息列表
     *
     * @param reqVo 分页查询对象
     */
    public SpringPageVO<EmployeeListRespVo> pageResult(EmployeePageReqVo reqVo) {
        PageRequest pa = reqVo.toPageRequest();
        Page<Employee> all = employeeRepository.findAll(new EmployeeSpecification(reqVo), pa);
        return SpringPageToSpringPageVO.of(all, po -> {
            EmployeeListRespVo listRespVo = new EmployeeListRespVo();
            if (po != null) {
                CopyUtils.copyProperties(po, listRespVo);
                listRespVo.setEmployeeId(String.valueOf(po.getId()));
            }
            return listRespVo;
        });
    }

    /**
     * 配送APP管理
     */
    public SpringPageVO<EmployeeTmsListRespVo> tmsPageResult(EmployeePageReqVo reqVo) {
        PageRequest pa = reqVo.toPageRequest();
        Page<Employee> all = employeeRepository.findAll(new EmployeeTmsSpecification(reqVo), pa);
        List<Employee> content = all.getContent();
        List<String> empCodes = Optional.ofNullable(content).orElse(new ArrayList<>()).stream()
                .map(Employee::getDepotCode).collect(Collectors.toList());
        List<UserEmpInfoRo> empRo = userEmpInfoRedisDao.findByIds(empCodes);
        Map<String, UserEmpInfoRo> map = Optional.ofNullable(empRo).orElse(new ArrayList<>()).stream()
                .collect(Collectors.toMap(UserEmpInfoRo::getEmployeeCode, ro -> ro));
        return SpringPageToSpringPageVO.of(all, po -> {
            EmployeeTmsListRespVo listRespVo = new EmployeeTmsListRespVo();
            if (po != null) {
                String empCode = po.getEmployeeCode();
                UserEmpInfoRo ro = map.get(empCode);
                CopyUtils.copyProperties(po, listRespVo);
                listRespVo.setEmpCode(po.getEmployeeCode());
                listRespVo.setQrCode(ro == null ? null : ro.getTicket());
            }
            return listRespVo;
        });
    }

    /**
     * ERP门店员工信息的推送处理方法
     *
     * @param reqVos 推送的变动信息
     */
    @EmployeeChangeMark
    @Transactional(rollbackFor = Exception.class)
    public void handelIncrEmp(List<IncrEmpReqVo> reqVos) {
        this.validate(reqVos);
        List<Employee> employees = Optional.ofNullable(reqVos)
                .orElse(Lists.newArrayList())
                .stream()
                .map(vo -> {
                    Employee employee = new Employee();
                    employee.setId(idService.getNextId());
                    CopyUtils.copyProperties(vo, employee);
                    return employee;
                }).collect(Collectors.toList());
        employeeRepository.save(employees);
        delayer.executeAfterTransactionCommit(() -> {
            employees.forEach(po -> {
                empIdCodeCache.refresh(po.getId());
                employeeCacheLoader.refresh(po.getEmployeeCode());
                empDepotListCache.refresh(po.getDepotCode());
            });
            this.register(employees);
        });
    }

    private void register(List<Employee> pos) {
        Set<String> empCodes = Optional.ofNullable(pos).orElse(Lists.newArrayList())
                .stream()
                .map(Employee::getEmployeeCode)
                .collect(Collectors.toSet());
        List<UserEmpInfo> userEmpInfo = empInfoRepository.findByEmployeeCodeIn(empCodes);
        Set<String> registed = Optional.ofNullable(userEmpInfo).orElse(Lists.newArrayList())
                .stream()
                .map(UserEmpInfo::getEmployeeCode)
                .collect(Collectors.toSet());
        empCodes.removeAll(registed);
        if (CollectionUtils.isEmpty(empCodes)) {
            return;
        }
        List<UserEmpInfo> list = empCodes.stream().map(code -> {
            UserEmpInfo info = new UserEmpInfo();
            info.setEmployeeCode(code);
            info.setId(idService.getNextId());
            return info;
        }).collect(Collectors.toList());
        empInfoRepository.save(list);
        List<UserEmpInfoRo> ros = Lists.transform(list, new UserEmpInfo2Ro());
        delayer.executeAfterTransactionCommit(() -> userEmpInfoRedisDao.save(ros));
    }

    /**
     * 记录注册推广信息
     *
     * @param vo 推广信息
     */
    @StreamListener(EmpStreamConstant.EMP_SPREAD)
    public void handeSpread(SpreadMsgVo vo) {
        String empCode = vo.getEmpCode();
        String mobile = vo.getMobile();
        Long registerId = vo.getRegisterId();
        Timestamp registerTime = vo.getRegisterTime();
        EmpSpread spread = new EmpSpread();
        spread.setId(idService.getNextId());
        spread.setEmployeeCode(empCode);
        Employee employee;
        try {
            employee = employeeCacheLoader.get(empCode);
        } catch (ExecutionException e) {
            e.printStackTrace();
            log.error("未获取到员工{}", empCode);
            throw new BizBusinessException(DepotException.EMP_NOT_EXIST);
        }
        Depot depot = depotRepository.findByDepotCode(employee.getDepotCode());
        AssertUtils.notNull(depot, DepotException.DEPOT_NOT_EXIT);
        spread.setEmpName(employee.getName());
        spread.setDepotName(depot.getName());
        spread.setDepotCode(depot.getDepotCode());
        spread.setMobile(mobile);
        spread.setRegisterId(registerId);
        spread.setRegisterTime(registerTime);
        empSpreadRepository.save(spread);
    }

    public SpringPageVO<EmpSpreadRespVo> getSpreadData(EmpSpreadPageReqVo reqVo) {
        Page<EmpSpread> page = empSpreadRepository.findAll(new EmpSpreadSpeccification(reqVo), reqVo.toPageRequest());
        return SpringPageToSpringPageVO.of(page, po -> {
            EmpSpreadRespVo listRespVo = new EmpSpreadRespVo();
            CopyUtils.copyProperties(po, listRespVo);
            return listRespVo;
        });
    }

    /**
     * 为员工生成二维码
     *
     * @param employeeCode 员工编号
     */
    @Transactional(rollbackFor = Exception.class)
    public QrCodeVo generateQrCode(String employeeCode) {
        AssertUtils.hasLength(employeeCode, DepotException.EMP_CODE_IS_NULL);
        UserEmpInfo userEmpInfo = empInfoRepository.findByEmployeeCode(employeeCode);
        if (userEmpInfo == null) {
            List<Employee> employees = employeeRepository.findByEmployeeCode(employeeCode);
            AssertUtils.notEmpty(employees, DepotException.EMP_NOT_EXIST);
            userEmpInfo = new UserEmpInfo();
            userEmpInfo.setEmployeeCode(employeeCode);
            userEmpInfo.setId(idService.getNextId());
        }
        if (StringUtils.isBlank(userEmpInfo.getTicket())) {
            QrCodeVo qrCode = this.createQrCode(employeeCode, getAccessToken());
            AssertUtils.notNull(qrCode, DepotException.EMP_TICKET_GENERAL_FAILED);
            AssertUtils.notNull(qrCode.getTicket(), DepotException.EMP_TICKET_GENERAL_FAILED);
            userEmpInfo.setTicket(qrCode.getTicket());
        }
        empInfoRepository.save(userEmpInfo);
        UserEmpInfoRo ro = new UserEmpInfo2Ro().apply(userEmpInfo);
        delayer.executeAfterTransactionCommit(() -> userEmpInfoRedisDao.save(ro));
        return new QrCodeVo(userEmpInfo.getTicket());
    }

    @Scheduled(fixedRate = 1000 * 60 * 2)
    public String refreshAccessToken() {
        try {
            String url = String.format(tokenUrl, appId, secret);
            WeChatAccessTokenVo token = restTemplate.getForObject(url, WeChatAccessTokenVo.class);
            log.info("刷新微信请求结果{}", token.toString());
            String access = token.getAccess_token();
            if (access == null) {
                log.error("刷新微信access_token出错,未获取到access_token");
            } else {
                log.info("刷新微信access_token:{}", access);
                weChatAccessRedisDao.setAccessToken(access);
            }
            return access;
        } catch (Exception e) {
            log.error("刷新微信access_token出错:{}", e.getMessage());
            e.printStackTrace();
        }
        return null;
    }

    private String getAccessToken() {
        return weChatAccessRedisDao.getAccessToken();
    }

    /**
     * 员工信息校验方法
     *
     * @param reqVos 需要校验的请求数据
     */
    private void validate(List<IncrEmpReqVo> reqVos) {
        AssertUtils.notEmpty(reqVos, DepotException.EMP_IS_NULL);
        for (IncrEmpReqVo reqVo : reqVos) {
            String employeeCode = reqVo.getEmployeeCode();
            String depotCode = reqVo.getDepotCode();
            EmployeeRankEnum identity = reqVo.getIdentity();
            String name = reqVo.getName();
            String mobile = reqVo.getMobile();
            AssertUtils.hasLength(employeeCode, DepotException.EMP_CODE_IS_NULL);
            AssertUtils.hasLength(name, DepotException.EMP_NAME_IS_NULL);
            AssertUtils.hasLength(mobile, DepotException.EMP_MOBILE_IS_NULL);
            AssertUtils.hasLength(depotCode, DepotException.DEPOT_CODE_IS_NULL);
            AssertUtils.notNull(identity, DepotException.EMP_IDENTITY_IS_NULL);
        }
    }


    /**
     * 获取二维码
     *
     * @param employeeCode 员工编码
     * @param accessToken  token
     * @return QrCodeVo
     */
    private QrCodeVo createQrCode(String employeeCode, String accessToken) {
        JSONObject scene = new JSONObject();
        scene.put("scene_str", employeeCode);
        JSONObject actionInfo = new JSONObject();
        JSONObject action = new JSONObject();
        actionInfo.put("scene", scene);
        action.put("action_name", "QR_LIMIT_STR_SCENE");
        action.put("action_info", actionInfo);
        String url = String.format(qrCodeTicketUrl, accessToken);
        try {
            String object = restTemplate.postForObject(url, action, String.class);
            log.info("员工生成二维码凭证结果{}", object);
            if (object.contains("ticket")) {
                return JSONObject.parseObject(object, QrCodeVo.class);
            } else {
                log.error("生成二维码编码失败{}", action);
            }
        } catch (Exception exception) {
            log.error("生成二维码编码失败{}", action);
        }
        return null;
    }

    private LoadingCache<String, List> empDepotListCache = CacheBuilder.newBuilder()
            .expireAfterWrite(30, TimeUnit.MINUTES)
            .maximumSize(1000)
            .build(new CacheLoader<String, List>() {
                @Override
                public List load(String key) throws Exception {
                    return employeeRepository.findByDepotCode(key, DateUtil.now());
                }
            });

    private LoadingCache<String, Employee> employeeCacheLoader = CacheBuilder.newBuilder()
            .expireAfterWrite(30, TimeUnit.MINUTES)
            .maximumSize(1000).build(new CacheLoader<String, Employee>() {

                @Override
                public Employee load(String key) throws Exception {
                    return employeeRepository.findByEmployeeCodeAndTime(key, DateUtil.now());
                }
            });

    private LoadingCache<Long, String> empIdCodeCache = CacheBuilder.newBuilder()
            .expireAfterWrite(1L, TimeUnit.HOURS)
            .build(new CacheLoader<Long, String>() {
                @Override
                public String load(Long key) throws Exception {
                    Employee one = employeeRepository.findOne(key);
                    return one.getEmployeeCode();
                }
            });

    public String getCodeById(Long employeeId) {
        try {
            return empIdCodeCache.get(employeeId);
        } catch (ExecutionException e) {
            log.error("获取员工编码失败{}", employeeId);
            e.printStackTrace();
        }
        return null;
    }

}